﻿// Inner Fire 游戏引擎库
// 场景分装 - 表面细分
//
// Copyright (c) 2023 by 尔西大帝. All rights reserved.
//
// 这个类分装各种场景的构建（都在一个文件里面太大了）
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-27

#include <dx_graphics.h>
#include <dx_scene/dx_scene.h>

namespace ifire {

void DxGraphics::Tess_Update() {
  UpdateObjectCBs();
  UpdateMaterialCBs();
  UpdateMainPassCB();
}

void DxGraphics::Tess_InitPipeline() {
  LoadTextures_Room();

  Tess_BuildMaterials();
  BuildRootSignature();
  BuildDescriptorHeaps_Room();

  if (scene_->ID() == DxSceneID::TESS_BASIC) {
    Tess_BuildShaderAndInputLayout();
    Tess_BuildQuadPatchGeometry();
    Tess_BuildRenderItems();
  } else {
    Tess_Patch_BuildShaderAndInputLayout();
    Tess_Patch_BuildQuadPatchGeometry();
    Tess_Patch_BuildRenderItems();
  }

  Tess_BuildFrameResources();
  Tess_BuildPSOs();
}

void DxGraphics::Tess_Draw() {
  auto cmd = command_list_.Get();
  auto cmdListAlloc = current_frame_resource_->CmdListAlloc;

  // Reuse the memory associated with command recording.
  // We can only reset when the associated command lists have finished execution
  // on the GPU.
  ThrowIfFailed(cmdListAlloc->Reset());

  // A command list can be reset after it has been added to the command queue
  // via ExecuteCommandList. Reusing the command list reuses memory.
  ThrowIfFailed(cmd->Reset(cmdListAlloc.Get(), pipelines_["Opaque"].Get()));

  cmd->RSSetViewports(1, &screen_viewport_);
  cmd->RSSetScissorRects(1, &scissor_rect_);

  // Indicate a state transition on the resource usage.
  auto to_render = CD3DX12_RESOURCE_BARRIER::Transition(GetBackBuffer(),
      D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
  cmd->ResourceBarrier(1, &to_render);

  // Clear the back buffer and depth buffer.
  auto dsv = GetDepthStencilView();
  auto back_buffer = GetBackBufferView();
  cmd->ClearRenderTargetView(
      back_buffer, (float*)&main_pass_.FogColor, 0, nullptr);
  cmd->ClearDepthStencilView(dsv,
      D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

  // Specify the buffers we are going to render to.
  cmd->OMSetRenderTargets(1, &back_buffer, true, &dsv);

  ID3D12DescriptorHeap* descriptorHeaps[] = {srv_descriptor_heap_.Get()};
  cmd->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

  cmd->SetGraphicsRootSignature(root_signature_.Get());

  UINT passCBByteSize =
      dxutils::calc_constant_buffer_size(sizeof(PassConstants));

  auto passCB = current_frame_resource_->PassCB->Resource();
  cmd->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress());

  DrawRenderItems(cmd, render_layer[(int)RenderLayer::Opaque]);

  // Indicate a state transition on the resource usage.
  auto to_present = CD3DX12_RESOURCE_BARRIER::Transition(GetBackBuffer(),
      D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
  cmd->ResourceBarrier(1, &to_present);

  // Done recording commands.
  ThrowIfFailed(cmd->Close());

  // Add the command list to the queue for execution.
  ID3D12CommandList* cmdsLists[] = {cmd};
  command_queue_->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

  // Swap the back and front buffers
  ThrowIfFailed(swap_chain_->Present(0, 0));
  current_back_buffer_ = (current_back_buffer_ + 1) % SWAP_CHAIN_BUFFER_COUNT;

  // Advance the fence value to mark commands up to this fence point.
  current_frame_resource_->Fence = ++current_fence_;

  // Add an instruction to the command queue to set a new fence point.
  // Because we are on the GPU timeline, the new fence point won't be
  // set until the GPU finishes processing all the commands prior to this
  // Signal().
  command_queue_->Signal(fence_.Get(), current_fence_);
}

void DxGraphics::Tess_BuildQuadPatchGeometry() {
  std::array<XMFLOAT3, 4> vertices = {XMFLOAT3(-10.0f, 0.0f, +10.0f),
      XMFLOAT3(+10.0f, 0.0f, +10.0f), XMFLOAT3(-10.0f, 0.0f, -10.0f),
      XMFLOAT3(+10.0f, 0.0f, -10.0f)};

  std::array<std::int16_t, 4> indices = {0, 1, 2, 3};

  const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
  const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);

  auto geo = std::make_unique<MeshGeometry>();
  geo->Name = "QuadpatchGeo";

  ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
  CopyMemory(
      geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

  ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
  CopyMemory(
      geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

  geo->VertexBufferGPU = CreateDefaultBuffer(
      vertices.data(), vbByteSize, geo->VertexBufferUploader);

  geo->IndexBufferGPU = CreateDefaultBuffer(
      indices.data(), ibByteSize, geo->IndexBufferUploader);

  geo->VertexByteStride = sizeof(XMFLOAT3);
  geo->VertexBufferByteSize = vbByteSize;
  geo->IndexFormat = DXGI_FORMAT_R16_UINT;
  geo->IndexBufferByteSize = ibByteSize;

  SubmeshGeometry quadSubmesh;
  quadSubmesh.IndexCount = 4;
  quadSubmesh.StartIndexLocation = 0;
  quadSubmesh.BaseVertexLocation = 0;

  geo->DrawArgs["quadpatch"] = quadSubmesh;

  geometries_[geo->Name] = std::move(geo);
}

void DxGraphics::Tess_Patch_BuildQuadPatchGeometry() {
  std::array<XMFLOAT3, 16> vertices = {
      // Row 0
      XMFLOAT3(-10.0f, -10.0f, +15.0f), XMFLOAT3(-5.0f, 0.0f, +15.0f),
      XMFLOAT3(+5.0f, 0.0f, +15.0f), XMFLOAT3(+10.0f, 0.0f, +15.0f),
      // Row 1
      XMFLOAT3(-15.0f, 0.0f, +5.0f), XMFLOAT3(-5.0f, 0.0f, +5.0f),
      XMFLOAT3(+5.0f, 20.0f, +5.0f), XMFLOAT3(+15.0f, 0.0f, +5.0f),
      // Row 2
      XMFLOAT3(-15.0f, 0.0f, -5.0f), XMFLOAT3(-5.0f, 0.0f, -5.0f),
      XMFLOAT3(+5.0f, 0.0f, -5.0f), XMFLOAT3(+15.0f, 0.0f, -5.0f),
      // Row 3
      XMFLOAT3(-10.0f, 10.0f, -15.0f), XMFLOAT3(-5.0f, 0.0f, -15.0f),
      XMFLOAT3(+5.0f, 0.0f, -15.0f), XMFLOAT3(+25.0f, 10.0f, -15.0f) //
  };

  std::array<std::int16_t, 16> indices = {
      0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

  const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
  const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);

  auto geo = std::make_unique<MeshGeometry>();
  geo->Name = "QuadpatchGeo";

  ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
  CopyMemory(
      geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

  ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
  CopyMemory(
      geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

  geo->VertexBufferGPU = CreateDefaultBuffer(
      vertices.data(), vbByteSize, geo->VertexBufferUploader);

  geo->IndexBufferGPU = CreateDefaultBuffer(
      indices.data(), ibByteSize, geo->IndexBufferUploader);

  geo->VertexByteStride = sizeof(XMFLOAT3);
  geo->VertexBufferByteSize = vbByteSize;
  geo->IndexFormat = DXGI_FORMAT_R16_UINT;
  geo->IndexBufferByteSize = ibByteSize;

  SubmeshGeometry quadSubmesh;
  quadSubmesh.IndexCount = (UINT)indices.size();
  quadSubmesh.StartIndexLocation = 0;
  quadSubmesh.BaseVertexLocation = 0;

  geo->DrawArgs["quadpatch"] = quadSubmesh;

  geometries_[geo->Name] = std::move(geo);
}

void DxGraphics::Tess_BuildMaterials() {
  auto whiteMat = std::make_unique<Material>();
  whiteMat->Name = "QuadMat";
  whiteMat->MatCBIndex = 0;
  whiteMat->DiffuseSrvHeapIndex = 3;
  whiteMat->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
  whiteMat->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);
  whiteMat->Roughness = 0.5f;

  materials_["WhiteMat"] = std::move(whiteMat);
}

void DxGraphics::Tess_BuildRenderItems() {
  auto quadPatchRitem = std::make_unique<RenderItem>();
  quadPatchRitem->World = MathHelper::Identity4x4();
  quadPatchRitem->TexTransform = MathHelper::Identity4x4();
  quadPatchRitem->ObjCBIndex = 0;
  quadPatchRitem->Mat = materials_["WhiteMat"].get();
  quadPatchRitem->Geo = geometries_["QuadpatchGeo"].get();
  // 这地方是不同的关键
  quadPatchRitem->PrimitiveType =
      D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST;
  quadPatchRitem->IndexCount =
      quadPatchRitem->Geo->DrawArgs["quadpatch"].IndexCount;
  quadPatchRitem->StartIndexLocation =
      quadPatchRitem->Geo->DrawArgs["quadpatch"].StartIndexLocation;
  quadPatchRitem->BaseVertexLocation =
      quadPatchRitem->Geo->DrawArgs["quadpatch"].BaseVertexLocation;
  render_layer[(int)RenderLayer::Opaque].push_back(quadPatchRitem.get());

  items_.push_back(std::move(quadPatchRitem));
}

void DxGraphics::Tess_Patch_BuildRenderItems() {
  auto quadPatchRitem = std::make_unique<RenderItem>();
  quadPatchRitem->World = MathHelper::Identity4x4();
  quadPatchRitem->TexTransform = MathHelper::Identity4x4();
  quadPatchRitem->ObjCBIndex = 0;
  quadPatchRitem->Mat = materials_["WhiteMat"].get();
  quadPatchRitem->Geo = geometries_["QuadpatchGeo"].get();
  // 这地方是不同的关键
  quadPatchRitem->PrimitiveType =
      D3D11_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST;
  quadPatchRitem->IndexCount =
      quadPatchRitem->Geo->DrawArgs["quadpatch"].IndexCount;
  quadPatchRitem->StartIndexLocation =
      quadPatchRitem->Geo->DrawArgs["quadpatch"].StartIndexLocation;
  quadPatchRitem->BaseVertexLocation =
      quadPatchRitem->Geo->DrawArgs["quadpatch"].BaseVertexLocation;
  render_layer[(int)RenderLayer::Opaque].push_back(quadPatchRitem.get());

  items_.push_back(std::move(quadPatchRitem));
}

void DxGraphics::Tess_BuildPSOs() {
  D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;

  //
  // PSO for opaque objects.
  //
  ZeroMemory(&opaquePsoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
  opaquePsoDesc.InputLayout = {
      input_layout_.data(), (UINT)input_layout_.size()};
  opaquePsoDesc.pRootSignature = root_signature_.Get();
  opaquePsoDesc.VS = {
      reinterpret_cast<BYTE*>(shaders_["TessVS"]->GetBufferPointer()),
      shaders_["TessVS"]->GetBufferSize()};
  opaquePsoDesc.HS = {
      reinterpret_cast<BYTE*>(shaders_["TessHS"]->GetBufferPointer()),
      shaders_["TessHS"]->GetBufferSize()};
  opaquePsoDesc.DS = {
      reinterpret_cast<BYTE*>(shaders_["TessDS"]->GetBufferPointer()),
      shaders_["TessDS"]->GetBufferSize()};
  opaquePsoDesc.PS = {
      reinterpret_cast<BYTE*>(shaders_["TessPS"]->GetBufferPointer()),
      shaders_["TessPS"]->GetBufferSize()};
  opaquePsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
  opaquePsoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME;
  opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
  opaquePsoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
  opaquePsoDesc.SampleMask = UINT_MAX;
  opaquePsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;
  opaquePsoDesc.NumRenderTargets = 1;
  opaquePsoDesc.RTVFormats[0] = back_buffer_format_;
  opaquePsoDesc.SampleDesc.Count = GetMsaaSampleCount();
  opaquePsoDesc.SampleDesc.Quality = GetMsaaSampleQuality();
  opaquePsoDesc.DSVFormat = depth_stencil_format_;
  ThrowIfFailed(device_->CreateGraphicsPipelineState(
      &opaquePsoDesc, IID_PPV_ARGS(&pipelines_["Opaque"])));
}

void DxGraphics::Tess_BuildShaderAndInputLayout() {
  shaders_["TessVS"] = dxutils::compile_shader(
      "Shaders\\Tessellation.hlsl", nullptr, "VS", "vs_5_0");
  shaders_["TessHS"] = dxutils::compile_shader(
      "Shaders\\Tessellation.hlsl", nullptr, "HS", "hs_5_0");
  shaders_["TessDS"] = dxutils::compile_shader(
      "Shaders\\Tessellation.hlsl", nullptr, "DS", "ds_5_0");
  shaders_["TessPS"] = dxutils::compile_shader(
      "Shaders\\Tessellation.hlsl", nullptr, "PS", "ps_5_0");

  input_layout_ = {{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
      D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}};
}

void DxGraphics::Tess_Patch_BuildShaderAndInputLayout() {
  shaders_["TessVS"] = dxutils::compile_shader(
      "Shaders\\BezierTessellation.hlsl", nullptr, "VS", "vs_5_0");
  shaders_["TessHS"] = dxutils::compile_shader(
      "Shaders\\BezierTessellation.hlsl", nullptr, "HS", "hs_5_0");
  shaders_["TessDS"] = dxutils::compile_shader(
      "Shaders\\BezierTessellation.hlsl", nullptr, "DS", "ds_5_0");
  shaders_["TessPS"] = dxutils::compile_shader(
      "Shaders\\BezierTessellation.hlsl", nullptr, "PS", "ps_5_0");

  input_layout_ = {{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
      D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}};
}

void DxGraphics::Tess_BuildFrameResources() {
  FrameResourceDesc desc = {};
  desc.PassCount = 2;
  desc.ObjectCount = static_cast<UINT>(items_.size());
  desc.MaterialCount = static_cast<UINT>(materials_.size());

  for (int i = 0; i < FRAME_RESOURCE_COUNT; i++) {
    frame_resources_.push_back(
        std::make_unique<FrameResource>(device_.Get(), &desc));
  }
}

} // namespace ifire